﻿/******************************************************************************
 * This file is part of libemb.
 *
 * libemb is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * libemb is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with libemb.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Project: Embedme
 * Author : FergusZeng
 * Email  : cblock@126.com
 * git	  : https://git.oschina.net/cblock/embedme
 * Copyright 2014~2017 @ ShenZhen ,China
*******************************************************************************/
#include "Logger.h"
#include "Tracer.h"
#include "Directory.h"
#include "File.h"
#include "Utils.h"

#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <dirent.h>

#include <list>

#include "log4z.h"
using namespace zsummer::log4z;

#define LOG_ID_MAX              (0xFFFF)        /**< 日志ID最大数 */
#define LOG_LENGTH_MAX		    1024		    /**< 每条日志最大长度 */

#define LOG_PATH_DEFAULT        "./log/"        /* 默认在当前工作目录下log文件夹 */

#define CORELOG_NAME_DEFAULT    "core"          /**< 日志文件后缀名 */
#define CORELOG_FILENUM_MAX     100             /**< 日志文件个数:100 */
#define CORELOG_LINES_MAX       100000          /**< 最大日志行数--10万行 */

#define DATALOG_NAME_DEFAULT     "data"

#define MAINLOG_NAME_DEFAULT     "main"          /**< 主日志名称  */
#define MAINLOG_FILESIZE_MAX      10              /**< 每个日志文件最大为10M */
#define MAINLOG_STORESIZE_MAX     1024            /**< 所有日志最大所占空间:1024M */

/**
 *  \brief  CoreLogger构造函数
 *  \param  none
 *  \return none
 */
CoreLogger::CoreLogger():
m_logfp(NULL),
m_lineNum(0),
m_logFileName(""),
m_initialized(false)
{
    m_logPath = LOG_PATH_DEFAULT;
    m_logName = CORELOG_NAME_DEFAULT;
    m_logFileNumMax = CORELOG_FILENUM_MAX;
}
/**
 *  \brief  CoreLogger析构函数
 *  \param  none
 *  \return none
 */
CoreLogger::~CoreLogger()
{
}

/**
 *  \brief  CoreLogger初始化
 *  \param  settings
 *  \return bool
 *  \note   仅允许初始化一次,目前仅支持以下三个配置项
 *  {
 *      logPath="/data/log";  //日志根目录
 *      logName="core";       //日志文件目录名称
 *      logFileNumMax=100;    //日志文件最大个数
 *  };
 *  最终将生成/data/log/core/core_20160101_080000.log文件
 */
bool CoreLogger::initWithSettings(Settings settings)
{
    m_logPath = settings["logPath"].toString();
    if (m_logPath.empty())
    {
        m_logPath = LOG_PATH_DEFAULT; 
    }
    m_logName = settings["logName"].toString();
    if (m_logName.empty())
    {
        m_logName = CORELOG_NAME_DEFAULT; 
    }
    m_logFileNumMax = settings["logFileNumMax"].toInt();
    if (m_logFileNumMax<=0 || m_logFileNumMax>CORELOG_FILENUM_MAX)
    {
        m_logFileNumMax = CORELOG_FILENUM_MAX; 
    }
    initialize(); 
    return true;
}
/**
 *  \brief  记录日志
 *  \param  fmt 参数
 *  \return void
 *  \note   none
 */
void CoreLogger::log(const char * fmt, ...)
{
    va_list argp;
    va_start(argp, fmt);
    makeLog(fmt, argp);
    va_end(argp);
}

std::vector<LogInfo_S> CoreLogger:: getLogInfoList()
{
    std::vector<LogInfo_S> logInfoList;
    LogInfo_S logInfo;
    if (m_initialized) 
    {
        logInfo.m_logName = m_logName;
        logInfo.m_logPath = m_logPath;
        logInfo.m_logSize = Directory::getContentSize(m_logPath.c_str()); 
        logInfoList.push_back(logInfo);
    }
    return logInfoList;
}

void CoreLogger::initialize()
{
    AutoLock lock(&m_logLock);
    if (!m_initialized)
    {
        char logFileName[128] = {0};
        time_t timep;
        struct tm * ptm = NULL;
        time(&timep);

        /* 拼接文件全名 */
    	std::string logFullName = m_logPath + "/";
        logFullName += m_logName;
        logFullName += "/";
        ptm = localtime(&timep);
        sprintf(logFileName, "core_%d%02d%02d_%02d%02d%02d.log",ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, \
                ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
        m_logFileName = std::string(logFileName);
        logFullName += m_logFileName;

        /* 先关闭日志文件 */
        if (m_logfp != NULL)
        {
            fclose(m_logfp);
            m_logfp = NULL;
        }
        /* 先清除旧日志文件 */
        if (!clearLog(m_logFileNumMax- 1))
        {
            TRACE_ERR_CLASS("clear log failed.\n");
            return;
        }

        /* 创建新文件,如果有同名文件会覆盖掉原来文件 */
        m_logfp = fopen(logFullName.c_str(), "w+");
        if (m_logfp == NULL)
        {
            TRACE_ERR_CLASS("Can't open log file:%s.\n", logFullName.c_str());
            return;
        }
        m_lineNum = 0;
        m_initialized = true;
    }
}

void CoreLogger::makeLog(const char * format, va_list argp)
{
    initialize();
    AutoLock lock(&m_logLock);

    char buf[LOG_LENGTH_MAX] = {0};
    char headBuf[64] = {0};
    struct tm * ptm;
    int nsize = 0;
    if (NULL == m_logfp)
    {
        TRACE_ERR_CLASS("Logger not init.\n");
        return;
    }

    if (0 == (m_lineNum % CORELOG_LINES_MAX))
    {
        /* 已达到最大条数,从文件头开始写 */
        fseek(m_logfp, 0, SEEK_SET);
    }

    time_t timep;
    time(&timep);
    ptm = localtime(&timep);
    sprintf(headBuf, "<L%d> [%d-%02d-%02d %02d:%02d:%02d]:", m_lineNum, \
            ptm->tm_year + 1900, ptm->tm_mon + 1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
    memcpy(buf, headBuf, strlen(headBuf));

    nsize = vsprintf(buf + strlen(headBuf), format, argp);
    fwrite(buf, 1, strlen(headBuf) + nsize, m_logfp);
    fwrite("\n",1,1,m_logfp);
    fflush(m_logfp);
    m_lineNum++;
}

bool CoreLogger::clearLog(int maxLogFiles)
{
    std::list<std::string> logFileList;
    DIR * dirp = NULL;
    struct dirent * dp = NULL;

    std::string logFullPath = m_logPath + "/";
    logFullPath += m_logName;
    if (!Directory::isExist(logFullPath.c_str())) 
    {
        if (!Directory::createDir(logFullPath.c_str(),true))
        {
            TRACE_ERR_CLASS("create dir:[%s] error:%s.\n", logFullPath.c_str(), ERROR_STRING);
            return false;
        }
    }
    dirp = opendir(logFullPath.c_str());
    if (NULL == dirp)
    {
        TRACE_ERR_CLASS("open dir:[%s] error:%s.\n", logFullPath.c_str(), ERROR_STRING);
        return false;
    }

    /* 扫描日志文件夹下所有文件 */
    while ((dp = readdir(dirp)) != NULL)
    {
        std::string fileName = dp->d_name;
        if (0 == fileName.compare(".") ||
            0 == fileName.compare(".."))
        {
            continue;
        }
        int len = fileName.size();
        std::string strFileName;
        if (len>m_logFileName.size())
        {
            strFileName = fileName.substr(len-m_logFileName.size(),m_logFileName.size());
        }
        else
        {
            strFileName = fileName;
        }
        if ((0 == strFileName.compare(0, m_logFileName.size(), fileName)) &&
            (fileName.size() == (m_logFileName.size())))
        {
            /* 如果log文件名匹配且长度匹配,则记录文件名 */
            logFileList.push_back(fileName);
        }
        else
        {
            /* 不匹配则删除该文件 */
            std::string logFullName = logFullPath+"/";
            logFullName += fileName;
            File::deleteFile(logFullName.c_str());
            TRACE_DBG_CLASS("============================\n");
            TRACE_DBG_CLASS("m_logFileName:%s\n",m_logFileName.c_str());
            TRACE_DBG_CLASS("fileName:%s\n",fileName.c_str());
            TRACE_DBG_CLASS("tmpStr:%s\n",strFileName.c_str());
        }
    }
    closedir(dirp);

    /* 找出要保留的最近maxLogfiles个文件,其他的删除 */
    int i = 0;
    int listSize = logFileList.size();
    logFileList.sort();	/* 按文件名升序排序 */
    std::list<std::string>::iterator iter = logFileList.begin();
    for (; iter != logFileList.end(); iter++, i++)
    {
        if ((listSize > maxLogFiles) &&
                (i < listSize - maxLogFiles))
        {
            std::string logFullName = logFullPath+"/";
            logFullName += (*iter);
            File::deleteFile(logFullName.c_str());
        }
    }
    return true;
}

DataLogger::DataLogger():
m_logDate("19000101"),
m_initialized(false)
{
    m_dbHandler = NEW_OBJ SqliteWrapper();
    m_logPath = LOG_PATH_DEFAULT;
    m_logName = DATALOG_NAME_DEFAULT;
}

DataLogger::~DataLogger()
{
    DEL_OBJ(m_dbHandler);
}

/**
 *  \brief  DataLogger
 *  \param  settings
 *  \return bool
 *  \note   仅允许初始化一次,目前仅支持以下两个配置项
 *  {
 *      logPath="/data/log";
 *      logName="data";
 *  };
 *  最终将生成"/data/log/data/data_20160101.db"文件
 */
bool DataLogger::initWithSettings(Settings settings)
{
    m_logPath = settings["logPath"].toString();
    if (m_logPath.empty())
    {
        m_logPath = LOG_PATH_DEFAULT; 
    }
    m_logName = settings["logName"].toString();
    if (m_logName.empty())
    {
        m_logName = DATALOG_NAME_DEFAULT; 
    }
    initialize();
    return true; 
}

/**
 *  \brief  记录日志
 *  \param  logid 日志id
 *  \param  fmt 参数
 *  \return void
 *  \note   none
 */
void DataLogger::log(int logType,const char * fmt, ...)
{
    va_list argp;
    va_start(argp, fmt);
    makeLog(logType,fmt, argp);
    va_end(argp);
}

std::vector<LogInfo_S> DataLogger:: getLogInfoList()
{
    std::vector<LogInfo_S> logInfoList;
    LogInfo_S logInfo;
    if (m_initialized) 
    {
        logInfo.m_logName = m_logName;
        logInfo.m_logPath = m_logPath;
        logInfo.m_logSize = Directory::getContentSize(m_logPath.c_str()); 
        logInfoList.push_back(logInfo);
    }
    return logInfoList;
}


void DataLogger::initialize()
{
    std::string logFullPath = m_logPath + "/";
    logFullPath += m_logName;
    if (!Directory::isExist(logFullPath.c_str())) 
    {
        if (!Directory::createDir(logFullPath.c_str(),true))
        {
            return;
        }
    }
    /* 比较时间,判断是否需要重新创建数据库 */
    char tmpStr[10] = {0};
    struct tm* ptm;
    time_t timep;
    time(&timep);
    ptm = localtime(&timep);
    sprintf(tmpStr, "%d%02d%02d",ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday);
    std::string date = std::string(tmpStr);
    if(m_logDate!=date)
    {
        m_initialized=false;
    }

    if (!m_initialized) 
    {
        std::string logFullName = m_logPath+"/";
        logFullName += m_logName;
        logFullName += "/log_";
        logFullName += date;
        logFullName += ".db";
        if (createLogDatabase(logFullName)) 
        {
            m_logDate = date;
        }
        m_initialized=true;
        m_dbFileName = logFullName;
    }
}

void DataLogger::makeLog(int logType,const char * fmt,va_list argp)
{
    initialize();
    AutoLock lock(&m_logLock);
    if (!m_dbHandler->open(m_dbFileName.c_str())) 
    {
        return;
    }
    /*****************************************************
     *  目前实测插入数据性能为大概8条记录每秒(x86测试):
     *  且已验证该性能与每次操作是否重新打开/关闭sqlite没有关系.
     ****************************************************/
    char buf[LOG_LENGTH_MAX] = {0};
    std::string sql = "insert into tbl_log(log_type,log_text) values(";
    sprintf(buf,"%d,",logType);
    sql += std::string(buf);
    sql += "'";
    vsnprintf(buf,LOG_LENGTH_MAX-1,fmt, argp);
    std::string logText = std::string(buf);
    logText = Utils::replaceString(logText,"'","''");/* 如果字符串中有单引号,则用两个单引号替换 */
    sql += logText;
    sql += "');";
    SqlResult_S result;
    if(!m_dbHandler->exec(sql,&result))
    {
        TRACE_ERR_CLASS("insert log failed.\n");
    }
    m_dbHandler->close();
}

bool DataLogger::createLogDatabase(const std::string fileName)
{
    if (File::isExist(fileName.c_str())) 
    {
        return true; 
    }
    m_dbHandler->close();
    if(!m_dbHandler->open(fileName))
    {
        TRACE_ERR_CLASS("open db: %s failed!\n",fileName.c_str());
        return false;
    }
    SqlResult_S result;
    std::string sql = "create table tbl_log(log_id INTEGER PRIMARY KEY AUTOINCREMENT,\
                       log_type INTEGER,log_time TIMESTAMP NOT NULL DEFAULT (datetime('now','localtime')),\
                       log_text TEXT);";
    if(!m_dbHandler->exec(sql,&result))
    {
        TRACE_ERR_CLASS("exec create tbl_log failed.\n");
        m_dbHandler->close();
        return false;
    }
    else
    {
        m_dbHandler->close();
        return true;
    }
}

/**
 *  \brief  MainLogger构造函数
 *  \param  none
 *  \return none
 */
MainLogger::MainLogger():
m_initialized(false)
{
    m_log4zLogInfos.clear();
    m_logPath = LOG_PATH_DEFAULT;
    m_logName = MAINLOG_NAME_DEFAULT;
    m_logFileSizeMax = MAINLOG_FILESIZE_MAX;
    m_logStoreSizeMax = MAINLOG_STORESIZE_MAX;
    m_monthDirEnable = true;

    /* 主日志必须有,因此先初始化 */
    Log4zLogInfo_S logger;
    logger.m_id = LOG4Z_MAIN_LOGGER_ID;
    logger.m_name = m_logName;
    logger.m_path = m_logPath+"/";
    logger.m_path += m_logName;/* 示例:logPath+logName="/data/log/main/" */
    m_log4zLogInfos.push_back(logger);
}
/**
 *  \brief  MainLogger析构函数
 *  \param  none
 *  \return none
 */
MainLogger::~MainLogger()
{
}

/**
 *  \brief  initWithSettings
 *  \param  settings
 *  \return bool
 *  \note   仅允许初始化一次,目前仅支持以下配置项
 *  {
 *      logName = "main";         //日志名称
        logPath="/data/log/mainlog";     //日志根目录
        logStoreMax=1024;        //日志最大存储容量,单位为M
        logFileSizeMax=10;       //日志文件最大大小,单位为M
        monthDirEnable=1;        //日志按月份建立文件夹
        loglist=(
        {name="sms";},
        {name="call";},
        );
 *  }
 *  最终生成日志目录:"/data/log/mainlog/main/","/data/log/mainlog/sms","/data/log/mainlog/call"
 */
bool MainLogger::initWithSettings(Settings settings)
{
    m_logPath = settings["logPath"].toString();
    if (m_logPath.empty())
    {
        m_logPath = LOG_PATH_DEFAULT; 
    }
    m_logName = settings["logName"].toString();
    if (m_logName.empty())
    {
        m_logPath = MAINLOG_NAME_DEFAULT; 
    }

    m_logFileSizeMax = settings["logFileSizeMax"].toInt();
    if (m_logFileSizeMax<=0 || m_logFileSizeMax>MAINLOG_FILESIZE_MAX)
    {
        m_logFileSizeMax = MAINLOG_FILESIZE_MAX;
    }

    int m_logStoreSizeMax = settings["logStoreMax"].toInt();
    if (m_logStoreSizeMax<=0 || m_logStoreSizeMax>MAINLOG_STORESIZE_MAX)
    {
        m_logStoreSizeMax = MAINLOG_STORESIZE_MAX;
    }
    m_monthDirEnable = !!(settings["monthDirEnable"].toInt());

    Log4zLogInfo_S mainLog;
    mainLog.m_id = LOG4Z_MAIN_LOGGER_ID;
    mainLog.m_name = m_logName;
    mainLog.m_path = m_logPath + "/";
    mainLog.m_path += m_logName;
    mainLog.m_path += "/";
    m_log4zLogInfos.clear();/* 清除掉默认初始化的配置 */
    m_log4zLogInfos.push_back(mainLog);

    /* 子日志 */
    int logListSize=settings["loglist"].size();
    for(int i=0; i<logListSize; i++)
    {
        std::string subLogName =settings["loglist"][i]["name"].toString();
        if (subLogName.empty())
        {
            continue;
        }
        Log4zLogInfo_S subLog;
        subLog.m_id = i+1;
        subLog.m_name = subLogName;
        subLog.m_path = m_logPath + "/";
        subLog.m_path += subLogName;
        subLog.m_path += "/";

        m_log4zLogInfos.push_back(subLog);
    }
    //TRACE_RED("main log in:%s\n",m_logPath.c_str());
    initialize();
    return true;
}

/**
 *  \brief  记录日志
 *  \param  name 日志名称
 *  \param  fmt 参数
 *  \return void
 *  \note   none
 */
void MainLogger::log(const std::string& name,const char * fmt, ...)
{
    /* 如果名称为name的子日志不存在,则默认保存在main日志中 */
    int logid = LOG_ID_MAIN;
    for(int i=0; i<m_log4zLogInfos.size(); i++)
    {
        if (m_log4zLogInfos[i].m_name == name)
        {
            logid = m_log4zLogInfos[i].m_id;
        }
    }
    va_list argp;
    va_start(argp, fmt);
    if (logid>=LOG_ID_MAIN && logid<=(LOG_ID_MAIN+LOG_ID_MAX))
    {
        makeLog(logid, fmt,argp);
    }
    va_end(argp);
}

std::vector<LogInfo_S> MainLogger:: getLogInfoList()
{
    std::vector<LogInfo_S> logInfoList;
    LogInfo_S logInfo;
    if (m_initialized) 
    {
        for (int i=0; i<m_log4zLogInfos.size(); i++ ) 
        {
            logInfo.m_logName = m_log4zLogInfos[i].m_name;
            logInfo.m_logPath = m_log4zLogInfos[i].m_path;
            logInfo.m_logSize = Directory::getContentSize(m_logPath.c_str()); 
            logInfoList.push_back(logInfo);
        }
    }
    return logInfoList;
}

void MainLogger::initialize()
{
    if (!m_initialized)
    {
        ILog4zManager* log4z = ILog4zManager::getInstance();
        for(int i=0; i<m_log4zLogInfos.size(); i++)
        {
            if (i!=0)
            {
                log4z->createLogger(m_log4zLogInfos[i].m_name.c_str());
            }
            log4z->enableLogger(m_log4zLogInfos[i].m_id, true);
            log4z->setLoggerPath(m_log4zLogInfos[i].m_id, m_log4zLogInfos[i].m_path.c_str());
            log4z->setLoggerDisplay(m_log4zLogInfos[i].m_id, true);              /* 显示日志信息 */
            log4z->setLoggerFileLine(m_log4zLogInfos[i].m_id, false);            /* 不显示文件及行数 */
            log4z->setLoggerLevel(m_log4zLogInfos[i].m_id, LOG_LEVEL_TRACE);     /* 日志级别 */
            log4z->setLoggerLimitsize(m_log4zLogInfos[i].m_id, m_logFileSizeMax);/* 最大文件大小 */
            log4z->setLoggerMonthdir(m_log4zLogInfos[i].m_id, m_monthDirEnable); /* 是否按月份新建文件夹 */
        }
        log4z->start();
        m_initialized=true;
    }
}

void MainLogger::makeLog(int logid,const char * format, va_list argp)
{
    initialize();
    AutoLock lock(&m_logLock);
    for(int i=0;i<m_log4zLogInfos.size();i++)
    {
        if (m_log4zLogInfos[i].m_id==logid)
        {
            char buf[LOG_LENGTH_MAX] = {0};
            vsnprintf(buf,LOG_LENGTH_MAX-1,format, argp);
            buf[LOG_LENGTH_MAX-1]=0;
            LOG_STREAM(logid,LOG_LEVEL_TRACE,"",0,buf);
        }
    }
}


LoggerFacade::LoggerFacade()
{
    m_logVect.clear();
    m_mainThread.start(this);
}

LoggerFacade::~LoggerFacade()
{
    m_logVect.clear();
    m_mainThread.cancel();
}

void LoggerFacade::coreLog(const std::string& log)
{
    AutoLock lock(&m_logMutex);
    LogContext_S logContext;
    logContext.m_logger = LOGGER_TYPE_CORE;
    logContext.m_text = log;
    m_logVect.push_back(logContext);
}

void LoggerFacade::dataLog(int type,const std::string& log)
{
    AutoLock lock(&m_logMutex);
    LogContext_S logContext;
    logContext.m_logger = LOGGER_TYPE_DATA;
    logContext.m_type = type;
    logContext.m_text = log;
    m_logVect.push_back(logContext);
}

void LoggerFacade::mainLog(const std::string& name,const std::string& log)
{
    AutoLock lock(&m_logMutex);
    LogContext_S logContext;
    logContext.m_logger = LOGGER_TYPE_MAIN;
    logContext.m_name = name;
    logContext.m_text = log;
    m_logVect.push_back(logContext);
}

void LoggerFacade::run() 
{
    while (1) 
    {
        m_logMutex.lock();
        if (m_logVect.size()>0) 
        {
            std::vector<LogContext_S>::iterator iter = m_logVect.begin();
            LogContext_S logContext = *iter;
            switch (logContext.m_logger) 
            {
                case LOGGER_TYPE_CORE:
                    CoreLogger::getInstance()->log("%s",logContext.m_text.c_str());
                    break;
                case LOGGER_TYPE_DATA:
                    DataLogger::getInstance()->log(logContext.m_type,"%s",logContext.m_text.c_str());
                    break;
                case LOGGER_TYPE_MAIN:
                    MainLogger::getInstance()->log(logContext.m_name,"%s",logContext.m_text.c_str());
                    break;
                default:
                    break;
            }
            m_logVect.erase(iter);
        }
        m_logMutex.unLock();
        Thread::msleep(10);
    }
}
